
#include <material/materialmanager.h>
#include <material/effecteditor/effecteditordialog.h>
#include <material/programeditor/programtreemodel.h>
#include <config.h>
#include <session.h>
#include <assert.h>
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <qevent.h>
#include <qmessagebox.h>

cceMaterialEffectPrimaryItem::cceMaterialEffectPrimaryItem ()
  : cceMaterialPrimaryItem (0)
{
  SetType ("<<Effect>>");
}

void cceMaterialEffectPrimaryItem::AddTechnique ()
{
  AddStageItem();

  // update the view
  GetView ()->UpdateLayout ();
	emit Changed();
}

cceMaterialEffectStageItem* cceMaterialEffectPrimaryItem::AddStageItem()
{
  cceMaterialEffectStageItem* item = new cceMaterialEffectStageItem (this);
	connect (item, SIGNAL(Changed()), this, SIGNAL(Changed()));
  connect (item, SIGNAL(ShowEditor(cceMaterialItem*)), this, SIGNAL(ShowEditor(cceMaterialItem*)));
  AddChild (item);
  return item;
}

void cceMaterialEffectPrimaryItem::RemoveStage(cceMaterialEffectStageItem* stage)
{
  RemoveChild (stage);
	disconnect (stage, SIGNAL(Changed()), this, SIGNAL(Changed()));
  disconnect (stage, SIGNAL(ShowEditor(cceMaterialItem*)), this, SIGNAL(ShowEditor(cceMaterialItem*)));

  // cleanup
  stage->deleteLater ();
  // update the view
  GetView ()->UpdateLayout ();
	emit Changed();
}

void cceMaterialEffectPrimaryItem::MoveEffectStage(cceMaterialEffectStageItem* stage, int direction)
{
  QList<cceMaterialBaseItem*>& children = GetChildrenList ();

  int idxOld = children.indexOf (stage);
  int idxNew = idxOld + direction;
  if (idxNew < 0 || idxNew >= children.size ())
    {
      return;
    }

  children.swap (idxOld, idxNew);

  // update the view
  GetView ()->UpdateLayout ();
	emit Changed ();
}

unsigned cceMaterialEffectPrimaryItem::GetNumberOfStages() const
{
  return GetChildren ().size ();
}

cceMaterialEffectStageItem* cceMaterialEffectPrimaryItem::GetStage(unsigned idx)
{
  return static_cast<cceMaterialEffectStageItem*>(GetChildrenList ()[idx]);
}

cceMaterialEffectStageItem::cceMaterialEffectStageItem (cceMaterialEffectPrimaryItem* primary)
  : cceMaterialTechniqueItem ()
  , _primary (primary)
{
  SetName ("<<EffectStage>>");

  for (unsigned i=0; i<Pass_COUNT; ++i)
    {
      _programs[i] = 0;
      _slots[i] = new cceMaterialProgramSlotItem (this, (cceMaterialPass)i);
      AddChild (_slots[i]);
    }
}



cceMaterialProgramItem* cceMaterialEffectStageItem::GetProgram(cceMaterialPass pass)
{
  return _programs[pass];
}


void cceMaterialEffectStageItem::RemoveTechnique()
{
  for (unsigned i=0; i<Pass_COUNT; ++i)
    {
      if (_programs[i])
        {
          RemoveChild(_programs[i]);
          _programs[i]->deleteLater();
        }
      else
        {
          RemoveChild(_slots[i]);
        }
      _slots[i]->deleteLater();
    }

  // remove the shaders here.
  _primary->RemoveStage(this);
}

void cceMaterialEffectStageItem::MoveUp ()
{
  _primary->MoveEffectStage(this, -1);
}

void cceMaterialEffectStageItem::MoveDown ()
{
  _primary->MoveEffectStage(this, 1);
}

void cceMaterialEffectStageItem::ChangeProgram(cceMaterialPass pass, cceMaterialProgram *program)
{
	SetProgram(pass, program);
	emit Changed ();
}

cceMaterialProgramItem* cceMaterialEffectStageItem::SetProgram(cceMaterialPass pass, cceMaterialProgram *program)
{
  RemoveAll();
  if (_programs[pass])
    {
      disconnect (_programs[pass], SIGNAL(ShowEditor(cceMaterialItem*)), this, SIGNAL(ShowEditor(cceMaterialItem*)));
      _programs[pass]->deleteLater();
      _programs[pass] = 0;
    }
  if (program)
    {
      _programs[pass] = new cceMaterialProgramItem (this, program, pass);
      connect (_programs[pass], SIGNAL(ShowEditor(cceMaterialItem*)), this, SIGNAL(ShowEditor(cceMaterialItem*)));
    }
  UpdateAll();
  return _programs[pass];
}

void cceMaterialEffectStageItem::RemoveProgram(cceMaterialPass pass)
{
  RemoveAll ();
  if (_programs[pass])
    {
      _programs[pass]->deleteLater();
      _programs[pass] = 0;
    }
  UpdateAll();
	emit Changed();
}

void cceMaterialEffectStageItem::RemoveAll()
{
  for (unsigned i=0; i<Pass_COUNT; ++i)
    {
      if (_programs[i])
        {
          RemoveChild(_programs[i]);
        }
      else
        {
          RemoveChild(_slots[i]);
        }
    }
}

void cceMaterialEffectStageItem::UpdateAll()
{
  for (unsigned i=0; i<Pass_COUNT; ++i)
    {
      if (_programs[i])
        {
          AddChild(_programs[i]);
        }
      else
        {
          AddChild(_slots[i]);
        }
    }

}


cceMaterialProgramSlotItem::cceMaterialProgramSlotItem (cceMaterialEffectStageItem* stage, cceMaterialPass pass)
  : cceMaterialSlotItem ()
  , _stage (stage)
  , _pass (pass)
{
  QString name = QString ("<<Attach %1>>").arg (cceMaterialEffectStage::GetPassName(pass));
  SetName (name);
}

namespace
{
cceMaterialProgram* read_program_from_mime (const QMimeData* mimeData)
{
  if (!mimeData)
    {
      return 0;
    }

  if (!mimeData->hasFormat (CCE_MATERIAL_PROGRAMNAME_MIME))
    {
      return 0;
    }

  QByteArray ba = mimeData->data (CCE_MATERIAL_PROGRAMNAME_MIME);
  return cceSession::Get ()->GetMaterialManager ()->GetProgram(QString (ba));
}
}

void cceMaterialProgramSlotItem::dragEnterEvent (QGraphicsSceneDragDropEvent* evt)
{
	cceMaterialProgram* program= read_program_from_mime (evt->mimeData ());
  if (program)
    {
      evt->accept ();
    }
  else
    {
      evt->ignore ();
    }
}

void cceMaterialProgramSlotItem::dropEvent (QGraphicsSceneDragDropEvent* evt)
{
	cceMaterialProgram* program = read_program_from_mime (evt->mimeData());
  if (!program)
    {
      return;
    }

	_stage->ChangeProgram(_pass, program);

  // update the view
  _stage->GetView ()->UpdateLayout ();
}


cceMaterialProgramItem::cceMaterialProgramItem (cceMaterialEffectStageItem* stage, cceMaterialProgram* program, cceMaterialPass pass)
  : cceMaterialValueItem ()
  , _stage(stage)
  , _program (program)
  , _pass (pass)
{
	setAcceptDrops(true);
  QString type = QString ("<<%1 Program>>").arg(cceMaterialEffectStage::GetPassName(pass));
  SetType(type);
  SetName (program->GetName ());
  connect (program, SIGNAL(Changed()), this, SLOT(onProgramChanged()));
  connect (program, SIGNAL(Removed()), this, SLOT(RemoveValue()));
  connect (this, SIGNAL(Clicked()), this, SLOT(ProgramClicked()));
}

void cceMaterialProgramItem::ProgramClicked()
{
  emit ShowEditor (_program);
}

void cceMaterialProgramItem::onProgramChanged()
{
  SetName (_program->GetName ());
  GetView()->UpdateLayout();
}


cceMaterialProgram* cceMaterialProgramItem::GetProgram()
{
  return _program;
}

bool cceMaterialProgramItem::IsProgram(cceMaterialProgram* program)
{
  return _program == program;
}

void cceMaterialProgramItem::RemoveValue ()
{
  _stage->RemoveProgram(_pass);
  _stage->GetView()->UpdateLayout();
}

void cceMaterialProgramItem::dragEnterEvent (QGraphicsSceneDragDropEvent* evt)
{
	cceMaterialProgram* program= read_program_from_mime (evt->mimeData ());
	if (program)
		{
			evt->accept ();
		}
	else
		{
			evt->ignore ();
		}
}

void cceMaterialProgramItem::dropEvent (QGraphicsSceneDragDropEvent* evt)
{
	cceMaterialProgram* program = read_program_from_mime (evt->mimeData());
	if (!program)
		{
			return;
		}

	_stage->ChangeProgram(_pass, program);

	// update the view
	_stage->GetView ()->UpdateLayout ();
}

cceMaterialEffectEditorDialog::cceMaterialEffectEditorDialog(QWidget* parent)
  : cceMaterialEditorAbstract (parent)
  , _effect (0)
  , _primaryItem (0)
{
  _gui.setupUi(this);

  _effectEditorView = new cceMaterialView (_gui.frameContent);
  QGridLayout* contentLayout = new QGridLayout (_gui.frameContent);
  contentLayout->setContentsMargins (0, 0, 0, 0);
  contentLayout->addWidget (_effectEditorView, 0, 0, 1, 1);
}

void cceMaterialEffectEditorDialog::SetEffect(cceMaterialEffect* effect)
{
  _effect = effect;
	connect (_effect, SIGNAL(Removed()), this, SLOT(ForceClose()));

	UpdateGUIFromEffect ();
}

void cceMaterialEffectEditorDialog::ForceClose()
{
  emit CloseEditor(_effect);
}

QString cceMaterialEffectEditorDialog::GetName() const
{
	if (_effect)
		{
			return _effect->GetName();
		}
	return "";
}

bool cceMaterialEffectEditorDialog::Save()
{
  return UpdateEffectFromGUI();
}


void cceMaterialEffectEditorDialog::on_lineEditName_textChanged (const QString& text)
{
  if (_primaryItem)
    {
			_primaryItem->SetName (text + ".fx");
      _effectEditorView->UpdateLayout ();
    }
  SetChanged(EffectChanged());
}


void cceMaterialEffectEditorDialog::Changed ()
{
  SetChanged(EffectChanged());
}

void cceMaterialEffectEditorDialog::UpdateGUIFromEffect ()
{
  assert (_effect);
	_gui.lineEditName->setText (GetEditorName(_effect->GetName ()));

  _primaryItem = new cceMaterialEffectPrimaryItem ();
  _primaryItem->SetName (_effect->GetName ());
	connect (_primaryItem, SIGNAL(Changed()), this, SLOT(Changed ()));
  connect (_primaryItem, SIGNAL(ShowEditor(cceMaterialItem*)), this, SIGNAL(ShowEditor(cceMaterialItem*)));

  for (unsigned i=0, j=_effect->GetNumberOfStages(); i<j; ++i)
    {
      cceMaterialEffectStage* stage = _effect->GetStage(i);
      cceMaterialEffectStageItem* stageItem = _primaryItem->AddStageItem();

      for (unsigned k=0; k<Pass_COUNT; ++k)
        {
          stageItem->SetProgram((cceMaterialPass)k, stage->GetPass((cceMaterialPass)k));
        }
    }


  _effectEditorView->SetItem (_primaryItem);
}

bool cceMaterialEffectEditorDialog::UpdateEffectFromGUI ()
{
  assert (_effect);

	cceMaterialEffect *fx = _manager->GetEffect(_gui.lineEditName->text() + ".fx");
	if (fx && fx != _effect)
		{
			QMessageBox::information(this,
															 "Effect - " CCE_TOOL_NAME,
															 "The effect name is already in use.\nPlease enter a unique effect name.");
			return false;
		}
	_effect->SetName (_gui.lineEditName->text () + ".fx");

  _effect->Clear ();

  for (unsigned i=0, j=_primaryItem->GetNumberOfStages(); i<j; ++i)
    {
      cceMaterialEffectStage* stage = new cceMaterialEffectStage();
      cceMaterialEffectStageItem* stageItem = _primaryItem->GetStage(i);
      _effect->AddStage(stage);

      for (unsigned k=0; k<Pass_COUNT; ++k)
        {
          cceMaterialPass pass = (cceMaterialPass)k;

          cceMaterialProgramItem* programItem = stageItem->GetProgram(pass);
          if (programItem)
            {
              cceMaterialProgram* program = programItem->GetProgram();
              stage->SetPass(pass, program);
            }
          else
            {
              stage->SetPass(pass, 0);
            }
        }
    }

  if (_effect->IsNew() && _manager)
    {
      _manager->AddEffect(_effect);
    }
	SetChanged(false);
  return true;
}

void cceMaterialEffectEditorDialog::Close ()
{
  if (EffectChanged ())
    {
			int result = QMessageBox::question (this,
				"Effect changed - " CCE_TOOL_NAME,
				"The effect has unsaved changes. Save changes bevor closing?",
				QMessageBox::Save, QMessageBox::Close, QMessageBox::Cancel);
			switch (result)
				{
				case QMessageBox::Save:
					Save ();
					break;

				case QMessageBox::Cancel:
					return;

				case QMessageBox::Close:
					break;
				}

    }
  emit CloseEditor(_effect);
}

bool cceMaterialEffectEditorDialog::EffectChanged ()
{
  if (!_effect || !_primaryItem)
    {
      return false;
    }
	if (_gui.lineEditName->text () != GetEditorName(_effect->GetName ()))
    {
      return true;
    }
  if (_effect->GetNumberOfStages() != _primaryItem->GetNumberOfStages())
    {
      return true;
    }

  for (unsigned i=0, j=_effect->GetNumberOfStages(); i<j; ++i)
    {
      cceMaterialEffectStage* stage = _effect->GetStage(i);
      cceMaterialEffectStageItem* stageItem = _primaryItem->GetStage(i);

      for (unsigned k=0; k<Pass_COUNT; ++k)
        {
          cceMaterialPass pass = (cceMaterialPass)k;
          cceMaterialProgram* program = stage->GetPass(pass);
          cceMaterialProgramItem* programItem = stageItem->GetProgram(pass);

          if (!program && !programItem)
            {
              continue;
            }
          if (program && programItem)
            {
              if (programItem->IsProgram(program))
                {
                  continue;
                }
            }
          return true;
        }
    }

  return false;
}

